home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 January: Mac OS SDK / Dev.CD Jan 97 SDK2.toast / Development Kits (Disc 2) / OpenDoc / Developer Documentation / Recipes, Tech Notes & Articles / Recipes / Data Interchange / Linking Recipes Part 3 < prev    next >
Encoding:
Text File  |  1995-12-08  |  23.1 KB  |  285 lines  |  [TEXT/ttxt]

  1. OpenDoc™ Recipes
  2.  
  3.  
  4. Linking Recipes Part 3
  5. By The OpenDoc Design Team
  6. December 8, 1995
  7.  
  8.  
  9. © 1993-1995  Apple Computer, Inc. All Rights Reserved.
  10. Apple, the Apple logo, AppleScript, Bento, Macintosh, QuickTime, and OpenDoc are 
  11. registered trademarks of Apple Computer, Inc.
  12. Finder, Mac, and QuickDraw are trademarks of Apple Computer, Inc. 
  13. SOM, SOMObjects, and System Object Model are licensed trademarks of IBM Corporation.
  14.  
  15.  
  16. Continued from Part 2...
  17.  
  18.  
  19. Updating a Link
  20.  
  21. Your part can use the MyWriteToLink method to update an existing link.    The updateID argument should be the updateID responsible for changing the content at the source of the link.  The justRewriting parameter should be kODFalse.  This recipe can be used to update both an automatic and a manual link.
  22.  
  23. In general, it is not necessary, and sometimes undesirable, to update links immediately in response to a content change.  When content is changing due to keyboard events, for example, it is reasonable to wait for a pause before updating any affected links.
  24.  
  25. Creating or Updating a Link Consisting of a Single Embedded Frame
  26.  
  27. If your part creates or updates a link source consisting of a single embedded frame, your part calls the embedded part's CloneInto method to supply the link content.  To allow the embedded part to promise its content, the embedded part must know to use the ODCloneKind value kODCloneToLink (instead of kODCloneCopy) in its call to BeginClone in its FulfillPromise method.  Before calling the embedded part's CloneInto method, your part should add a kODPropCloneKindUsed property to the link source object's content storage unit, add the value type kODCloneKind, and write the value kODCloneToLink as a 32-bit integer.  For example:
  28.  
  29.     ODSetULongProp(ev, contentSU, kODPropCloneKindUsed, kODCloneKind, kODCloneToLink);
  30.  
  31. This is demonstrated in the example routine MyWriteToLink in this document.
  32.  
  33. Notes on Implementing FulfillPromise
  34.  
  35. All parts should check for the presense of a kODPropCloneKindUsed property in their FulfillPromise method.  The following code can be used to determine the correct clone kind argument to BeginClone:
  36.  
  37.     ODCloneKind cloneKind = kODCloneCopy;
  38.     ODStorageUnit* promiseSU = promiseSUView->GetStorageUnit(ev);
  39.     if ( ODSUExistsThenFocus(ev, promiseSU, kODPropCloneKindUsed, kODCloneKind) )
  40.       cloneKind = ODGetULongProp(ev, promiseSU, kODPropCloneKindUsed, kODCloneKind);
  41.  
  42. The default clone kind is kODCloneCopy unless a specific clone kind is specified by a kODPropCloneKindUsed property.
  43.  
  44. Notes on CloneInto When Called to Write to a Link
  45.  
  46. Parts must be aware that links are never written into a link.  Parts that write content directly into a link they maintain, or parts who’s CloneInto method is called to write content into a link, must handle cloning of link objects as described in the next paragraph.  A part’s CloneInto method doesn’t know when its called to clone into a link, so it must follow this recipe all the time.
  47.  
  48. If a part calls its draft's Clone method on a link or link source object, and the destination of the clone is a link, Clone will return the object ID kODNULLID.  When Clone returns kODNULLID, the object will not be cloned. Attempting to call a storage unit's GetStrongStorageUnitRef with a null object id will cause an exception to be returned.  A part should check the result of Clone by calling ODDraft::IsValidID; if IsValidID returns kODFalse, it should write any linked content as it would if no link were involved.  In most cases, the part will simply not write out the link or link source object reference and whatever other data it associates with the link.
  49.  
  50. Note that if a part has not been internalized, it may be cloned into a link via its storage unit’s CloneInto method.  Any references to link or link source objects from the storage unit will be copied but won’t be valid because the referenced objects won’t be cloned.  Parts must always validate persistent references to link and link source objects before internalizing them in their InitPartFromStorage method, and abandon the link if the reference is invalid.
  51.  
  52. Updating the Destination of a Link
  53.  
  54. A part can update the destination of a link in its LinkUpdated method, which is called automatically if the part registers as a dependent of a link, or in response to the user's request to update a manual link.  The example method MyUpdateLinkDestination provides the skeleton structure; add code to incorporate or embed the data into your content.  You should be able to use a common routine to integrate data whether it comes from a link, the clipboard, or a drop.
  55.  
  56. If the last update at the source of a link failed, leaving a link without content, destinations that try to read the link will get the error kODErrNoLinkContent back from GetContentStorageUnit.  This ensures that a destination cannot attempt to embed a storage unit without content as a part.  Your part should consider this a temporary problem and refrain from updating at this time.  The MyUpdateLinkDestination routine shown here will raise this exception to the caller, which should just ignore the error.
  57.  
  58. If, however, your part gets back the error kODErrCannotEstablishLink from GetContentStorageUnit, this means that the link could not be established correctly.  This error will only be returned after a link has been returned by your draft’s AcquireLink method, but before any content is available through it.  In response to this error, your part should remove the link from its content model, release its reference to the link, and alert the user.  GetContentStorageUnit can only return kODErrCannotEstablishLink during the initial automatic update of a cross-document link, which occurs asynchronously after a drop or paste has completed.  A part can assume this error won’t arise inside the code implementing a drop or paste.
  59.  
  60. Your part may encounter invalid persistent references to link or link source objects when updating from a link.  The appropriate response is to ignore them and treat the content they were associated with as unlinked content.
  61.  
  62. Note that updating the destination of a link is not an undoable operation.  You should not create an undo action for it.
  63.  
  64. For future compatibility, this recipe does not assume the link content storage unit is in the same draft as the destination part.
  65.  
  66. When updating a manual link destination, use one new unique update ID to propagate changes to affected link sources and display frames, but save the update ID from the ODLink object in the associated ODLinkInfo structure.  The update id in the link info structure is used to determine if the “Update Now” button in the Link Info dialog should be disabled because no new content is available from the link.
  67.  
  68. void MyUpdateLinkDestination(Environment *ev,
  69.     ODLink* link,
  70.     ODLinkInfo* linkInfo)
  71. {
  72.   AppleTestDrag_DragText *fSOMSelf;
  73.   AppleTestDrag_DragTextData *fSOMThis = AppleTestDrag_DragTextGetData(fSOMSelf);
  74.  
  75.   ODLinkKey linkKey;
  76.  
  77.   ODVolatile(link);
  78.   ODVolatile(linkKey);
  79.  
  80.   ODFrame* myDisplayFrame;
  81.   ODFrame* anEmbeddedFrame;
  82.  
  83.   if ( link->Lock(ev, 0, &linkKey) )
  84.   {
  85.     TRY
  86.  
  87.       ODStorageUnit* linkContentSU = link->GetContentStorageUnit(ev, linkKey);
  88.       ODDraft* sourceDraft = linkContentSU->GetDraft(ev);
  89.  
  90.       // Remove the current content at the destination of the link.
  91.       //   This is part specific.
  92.       …
  93.  
  94.       // Insert content from the link into the destination.
  95.       fSOMSelf->MyReadFromContentSU(ev, 
  96.           linkContentSU, 
  97.           kODNULL, 
  98.           kODCloneFromLink, 
  99.           kODInLinkDestination);
  100.  
  101.       // Update the link info for this destination
  102.       linkInfo->change = link->GetUpdateID(ev);
  103.       linkInfo->changeTime = link->GetChangeTime(ev);
  104.  
  105.       // If this is a manual link destination that registered for updates
  106.       //   in order to receive the initial link content, unregister now if
  107.       //   there are no other automatic destinations of this link.
  108.       //   Your part may need to pass in more information to make this
  109.       //   determination.
  110.       if ( !linkInfo->autoUpdate && … )
  111.         link->UnregisterDependent(ev, somThis->fPartWrapper);
  112.  
  113.     CATCH_ALL
  114.  
  115.       link->Unlock(ev, linkKey);
  116.       RERAISE;
  117.  
  118.     ENDTRY
  119.  
  120.     link->Unlock(ev, linkKey);
  121.  
  122.     // If this is a manually updated link destination, generate a new update id
  123.     //   to use in propagating the change.  Automatically updated link destinations
  124.     //   must propagate the link’s update id.
  125.     ODUpdateID notifyUpdateID;
  126.     if ( linkInfo->autoUpdate )
  127.       notifyUpdateID = linkInfo->change;
  128.     else
  129.       notifyUpdateID = somSelf->GetStorageUnit(ev)->GetSession(ev)->UniqueUpdateID(ev);
  130.  
  131.     // Call ODLinkSource::ContentUpdated using notifyUpdateID for each source link 
  132.     //   maintained by this part and which contains the destination being updated.
  133.     …
  134.  
  135.     // Any time this part's content is changed, notify all containing parts.
  136.     //   This method should be called for all frames displaying the changed
  137.     //   content.
  138.     myDisplayFrame->ContentUpdated(ev, notifyUpdateID);
  139.  
  140.     // Make sure that this change is saved when the document is closed
  141.     fSOMSelf->GetStorageUnit(ev)->GetDraft(ev)->SetChangedFromPrev(ev);
  142.   }
  143. }
  144.  
  145. Link Source Moved Across Parts
  146.  
  147. OpenDoc allows content to be moved across parts; if the content contains links, the links are preserved if the destination uses a format supporting links.  (If the destination uses a format that doesn't specify linking, links will be broken at the source.)  If content is moved to a part that supports different content kinds, it may not be able to write all kinds into the link that the original source part could.  Parts don't need to record content kinds they write to a link as part of the information they associate with a link source.  When its time to update the link, they should just write the kinds irrespective of what might already be in the link.  The Clear method of ODLinkSource will ensure that any obsolete value kinds are removed from the link.
  148.  
  149. At the destination of a link, a part must be prepared to deal with the content kind missing from the link.  If possible, the part should attempt to use another format from the link.  Otherwise, the destination should break the link and leave the existing content in place.
  150.  
  151. Update IDs
  152.  
  153. All linked content, that is, every region of content at the source or destination of a link, has an update ID associated with it.  The source part determines the update ID associated with the link.  The destination part is responsible for remembering the update ID of the link at the time the destination last updated from the link.
  154.  
  155. Parts are responsible for correctly propagating update IDs to enable OpenDoc to detect circular links.  If a circular link goes undetected, the parts involved can update each other indefinitely.  When updating a link, the source part should reuse the update ID associated with the content causing the link to update.  If the content change was due to an automatically updated link destination, the update ID of the link destination should be adopted as the update ID of the affected link.  If, however, the content change was due to an manually updated link destination, generate a new update ID to use to update all affected link sources and display frames.  See the sample routine MyUpdateLinkDestination  for an example of propagating changes resulting from a manually-updated link destination.
  156.  
  157. If a content change originates in a part, such as in response to keyboard events, all links directly affected should be associated with the same new update ID.  A new update ID is created by calling the UniqueUpdateID method:
  158.  
  159. ODUpdateID updateID = 
  160.   somSelf->GetStorageUnit(ev)->GetSession(ev)->UniqueUpdateID(ev);
  161.  
  162. Your part must not create a distinct update ID for each affected link source; use the same ID for all of them.
  163.  
  164. Links Containing Embedded Frames
  165.  
  166. Links may be established to content containing embedded frames, just as content containing embedded frames may be copied and pasted without a persistent link.  The destination of such a link contains a copy of the parts embedded at the source of the link.  When the destination is updated, the previously embedded parts are discarded, and replaced by new ones cloned from the link.  Display frames for the new parts are hooked into the frame hierarchy at the destination, and facets are created for the currently visible frames.
  167.  
  168. One special case is a link to one embedded frame.  If the user copies a single embedded frame to the clipboard, the active part should clone the embedded part into the content storage unit of the clipboard, and then create a kODPropProxyContent property in the same content storage unit in which container-specific annotations about the embedded frame are written (see the Data Interchange Basics document for details of this recipe).  If a paste with link is performed at the destination, a link is created to the embedded frame, without involving other intrinsic content of the containing part.  Should that embedded frame be again cut or copied to the clipboard, at either the source or destination of the link, the embedded part should be cloned into the content storage unit of the clipboard, as before.  After the embedded part has cloned itself, the containing part should add a proxy content property to the same content storage unit, and write out a value that includes the link or link source (cloned to the clipboard and referenced by the proxy content).  When the clipboard content is pasted into any part that understands the value type in the proxy content, the link can be preserved.
  169.  
  170. Frame Link Status
  171.  
  172. All frames have a link status that describes the frame's participation in links.  The link status of a frame should be set to indicate whether it is embedded in the source of a link, in the destination of a link, or not involved in any link. Parts can use this information to determine when they should not allow creation of a new link, or not allow content to be cut.
  173.  
  174. All parts must set the link status of frames they embed, even parts that don't otherwise support linking.  Whenever frames are embedded during data interchange, the active part must set the link status of each embedded frame using the ChangeLinkStatus method, even if a link isn’t created.  Also, a part’s LinkStatusChanged method is responsible for notifying its embedded frames, as described below under “Implementing LinkStatusChanged.”
  175.  
  176. When a link is created, a part should call ODFrame::ChangeLinkStatus for all of its frames that display data at the source or destination of the link. ChangeLinkStatus will change the value of the link status (ODLinkStatus type) after examining the link status of its containing frame. ChangeLinkStatus will call ODPart::LinkStatusChanged for its displayed part in order to give the part the chance to call ChangeLinkStatus for any of its embedded frames. Frames and parts can examine the link status of a frame by calling ODFrame::GetLinkStatus.
  177.  
  178. If incorporation adds new embedded frames to the receiving part, the link status of those embedded frames must be set by calling their ChangeLinkStatus method.  If an embedded frame lies within the destination of a link maintained by this part, its status should be changed to kODInLinkDestination (as would be the case when this is a paste with link operation).  Otherwise, if the embedded frame lies within the source of a link, its status should be changed to kODInLinkSource.  (When an embedded frame is contained in both a link source and a link destination, it's status should be set to kODInLinkDestination.)  If the embedded frame is not within any link maintained by this part, its status should be changed to kODNotInLink.
  179.  
  180. When a part breaks a link, the part needs to call the ChangeLinkStatus method of all affected embedded frames.  The new status can be just the embedded frame’s status with respect to the part, since ChangeLinkStatus takes the containing frame’s status into account.
  181.  
  182. Parts are responsible for ensuring that the link status of an embedded frame is correct when it internalizes the frame.  Parts that don’t support linking can simply call the ChangeLinkStatus method of the embedded frame, specifying kODNotInLink.  Parts that do support linking should set the link status according to the embedded frame’s participation in links that the part maintains.  The frame object will do nothing if the status is unchanged, and will take the containing frame’s status into consideration to simplify the part’s responsibilities.
  183.  
  184. Implementing LinkStatusChanged
  185.  
  186. The LinkStatusChanged method is called to notify a part that one of its display frames was included in the creation or deletion of a link.  The part must pass this notification along to any embedded frames by calling ODFrame::ChangeLinkStatus.  As an optimization, embedded frames which are already involved in links managed by this part don't need to be notified, since their status doesn’t change (if the embedded frame is in a destination maintained by your part, its still in a destination; if its in a source, a destination could not be created around it).
  187.  
  188. Content Change Protocol
  189.  
  190. Because linked content may contain embedded frames, some protocol between parts is required to inform containing parts of changes to embedded content.  Two methods are involved when content changes:
  191.  
  192. void ODFrame::ContentUpdated(Environment* ev,
  193.           ODUpdateID change);
  194.  
  195. void ODPart::EmbeddedFrameUpdated(Environment* ev, 
  196.           ODFrame* frame,
  197.           ODUpdateID change);
  198.  
  199. If a content change in a part affects one of its display frames, the part should call ODFrame::ContentUpdated, passing it the unique update ID for the change. This unique ID may be obtained through a call to ODSession::UniqueUpdateID if the change originated in the part (or if the change was the manual updating of a link destination). ODFrame::ContentUpdated will call ODPart::EmbeddedFrameUpdated for all containing parts in the frame hierarchy.  The containing parts then know that some of its embedded content has changed. If the embedded part is involved in a link source, the containing part maintaining the link can choose to update the link with the new data.
  200.  
  201. Implementing EmbeddedFrameUpdated
  202.  
  203. The EmbeddedFrameUpdated method is called to notify a part that some change to the content of an embedded frame has occurred.  If the part maintains the source of a link that includes the embedded frame, the part should update the link.  The part is passed a reference to the embedded frame, and the update ID associated with the modification.
  204.  
  205. Calling ODFrame::ContentUpdated
  206.  
  207. When content displayed by a frame is changed, call the affected frame's ContentUpdated method.  The display frame will pass the notification on to all containing parts, unless the frame is the root frame of the document.
  208.  
  209. Displaying Link Info Dialogs
  210.  
  211. Recipes for displaying the link info dialogs are included in the Info recipe document.
  212.  
  213. Displaying Link Borders
  214.  
  215. A border should be drawn around a link when the link is selected or when the "Show Links" setting is checked in the Part Info dialog.
  216.  
  217. Whenever a part draws its content, it should call the ShouldShowLinks method of the facet's window.  If ShouldShowLinks returns kODTrue, borders should be drawn.
  218.  
  219. if ( myFacet->GetWindow(ev)->ShouldShowLinks(ev) )
  220. {
  221.   // Draw borders around links in this facet.
  222. }
  223.  
  224. Implementing RevealLink
  225.  
  226. Parts that create links need to implement the RevealLink method to enable users to navigate from the destination of a link to the source content.  RevealLink may be called when the part's document is not the frontmost process; bring the process to the front, if necessary.  If the link cannot be made visible in an existing display frame, the part should open a new window.
  227.  
  228. This is the rough outline of a typical RevealLink implementation.  It uses four methods not defined here:  MySetFrontProcess, MyBestDisplayFrame, MyActivateFrame, and MyScrollToLink.
  229.  
  230. SOM_Scope ODLinkSource* SOMLINK MyPartRevealLink(MyPart *somSelf, Environment *ev,
  231.     ODLinkSource* linkSource)
  232. {
  233.   MyPartData *somThis = MyPartGetData(somSelf);
  234.  
  235.   // Make sure this process is frontmost
  236.   somSelf->MySetFrontProcess(ev);
  237.  
  238.   // Choose a display frame for the source content.
  239.   //   This is part specific.  If no suitable display frame exists,
  240.   //   the part should open a new window 
  241.  
  242.   ODFrame* frame = (ODFrame*) somSelf->MyBestDisplayFrame(ev, linkSource);
  243.  
  244.   // If the best frame has a containing frame, ensure the display
  245.   //   frame is revealed.
  246.  
  247.   ODFrame* containingFrame = frame->AcquireContainingFrame(ev);
  248.   if ( containingFrame != kODNULL )
  249.   {
  250.     ODPart* containingPart = kODNULL;
  251.  
  252.     TRY
  253.       containingPart = containingFrame->AcquirePart(ev);
  254.       containingPart->RevealFrame(ev, frame, kODNULL);
  255.     CATCH_ALL
  256.     ENDTRY
  257.  
  258.     ODReleaseObject(ev, containingPart);
  259.     ODReleaseObject(ev, containingFrame);
  260.   }
  261.  
  262.   // Activate my frame by using my normal activation method.
  263.   //   RevealLink may be called when the part's document is not the
  264.   //   frontmost process, so be sure to bring it to the front if
  265.   //   necessary.
  266.  
  267.   somSelf->MyActivateFrame(ev, frame);
  268.  
  269.   // Scroll my frame as necessary to make the source content visible.
  270.  
  271.   somSelf->MyScrollToLink(ev, linkSource);
  272. }
  273.  
  274. Editing a Part Embedded in a Link Destination
  275.  
  276. If your display frame's link status is kODInLinkDestination (determined by calling ODFrame::GetLinkStatus), the frame is embedded in a link destination and editing is not usually allowed.  If the user attempts to edit content in the frame, the part should call the EditInLink method of the display frame.  In response, OpenDoc will call the EditInLinkAttempted method of the part maintaining the destination of the link.  Parts that support linking and embedding need to override this method.
  277.  
  278. EditInLinkAttempted should check that it maintains a link destination including the argument embedded frame.  If not, it should return kODFalse.  If it does maintain a link destination, it should present an alert informing the user of the attempted edit to a link destination, and allow the user to find the source of the link or break the destination link (see the OpenDoc Human Interface Specification for a description of the recommended behavior).  In either case, the part should not activate one of its display frames.  If the user chooses to break the link, the part should change the link status of all affected embedded frames.  An alert for this purpose is included in the Example Part Resources document in the Sample Code folder (the 'ALRT' resource is named “Sample Destination Link Error”).
  279.  
  280. ODFrame::EditInLink returns kODFalse if the part maintaining the link destination could not be found.  In this unlikely event, the part should put up a simple alert informing the user that editing the destination of a link is not allowed.  An alert for this purpose is included in the Example Part Resources document in the Sample Code folder (the 'ALRT' resource is named “Can’t change destination link”).
  281.  
  282. Editing Intrinsic Content at a Link Destination
  283.  
  284. The Example Part Resources document in the Sample Code folder contains alerts that a part can display when the user attempts to modify a selection containing one or more link destinations maintained by the part.  If the selection is confined to a single link destination, the same dialog used for impementing EditInLinkAttempted should be displayed (the 'ALRT' resource named “Sample Destination Link Error”).  If the selection includes content from more than one destination link, the 'ALRT' resource named “Multiple Destination Link Error” should be used. 
  285.